package demo.interceptor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.MappedInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

/**
 * <h1>拦截器：</h1>依赖于web框架，在SpringMVC中就是依赖于SpringMVC框架。在实现上基于Java的反射机制，属于面向切面编程（AOP）的一种运用。
 * 由于拦截器是基于web框架的调用，因此可以使用Spring的依赖注入（DI）进行一些业务操作，同时一个拦截器实例在一个controller生命周期之内可以多次调用。
 * 但是缺点是只能对controller请求进行拦截，对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理
 * <p>
 * <p>
 * Interceptor（即处理器拦截器、拦截器），类似于 Servlet 开发中的过滤器 Filter，用于对处理器（Controller）进行预处理和后处理。 常见的应用场景：
 * <li>日志记录：记录请求信息的日志，以便进行信息监控、信息统计、计算PV（Page View，即页面浏览量）等。
 * <li>权限检查：如登录检测，进入处理器检测检测是否登录，如果没有直接返回到登录页面。
 * <li>性能监控：有时候系统在某段时间莫名其妙的慢，可以通过拦截器在进入处理器之前记录开始时间，在处理完后记录结束时间，从而得到该请求的处理时间（如果有反向代理，如apache可以自动记录）。
 * <li>通用行为：读取 cookie 得到用户信息并将用户对象放入请求，从而方便后续流程使用。
 * <p>
 * 在SpringMVC的配置文件中注册拦截器
 *
 * <pre>
 * <!-- 拦截器 -->
 * <mvc:interceptors>
 * <!-- 对所有请求都拦截，公共拦截器可以有多个 -->
 * <bean name="baseInterceptor" class="cn.zifangsky.interceptor.BaseInterceptor" />
 * <mvc:interceptor>
 * <!-- 对/test.html进行拦截 -->
 * <mvc:mapping path="/test.html"/>
 * <!-- 特定请求的拦截器只能有一个 -->
 * <bean class="cn.zifangsky.interceptor.TestInterceptor" />
 * </mvc:interceptor>
 * </mvc:interceptors>
 * </pre>
 *
 * <h1>拦截器（Interceptor）和过滤器（Filter）的区别</h1>
 * Spring的Interceptor(拦截器)与Servlet的Filter有相似之处，比如二者都是AOP编程思想的体现，都能实现权限检查、日志记录等。不同的是：
 * <li>Filter 接口定义在 javax.servlet 包中 ; 接口 HandlerInterceptor 定义在org.springframework.web.servlet 包中
 * <li>Filter 定义在 web.xml 中
 * <li>Filter 在只在 Servlet 前后起作用。Filters 通常将 请求和响应（request/response） 当做黑盒子，Filter 通常不考虑servlet 的实现。
 * 拦截器能够深入到方法前后、异常抛出前后等，因此拦截器的使用具有更大的弹性。允许用户介入（hook into）请求的生命周期，
 * 在请求过程中获取信息，Interceptor 通常和请求更加耦合。
 * 在Spring构架的程序中，要优先使用拦截器。几乎所有 Filter 能够做的事情， interceptor 都能够轻松的实现
 * <li>Filter 是 Servlet 规范规定的。 而拦截器既可以用于Web程序，也可以用于Application、Swing程序中。 使用范围不同
 * <li>Filter 是在 Servlet 规范中定义的，是 Servlet 容器支持的。 而拦截器是在 Spring容器内的，是Spring框架支持的。 规范不同
 * <li>Filter 不能够使用 Spring 容器资源 拦截器是一个Spring的组件，归Spring管理，配置在Spring文件中，因此能使用Spring里的任何资源、对象，例如
 * Service对象、数据源、事务管理等，通过IoC注入到拦截器即可 Spring 中使用 interceptor 更容易
 * <li>Filter 是被 Server(like Tomcat) 调用 Interceptor 是被 Spring 调用 因此Filter 总是优先于 Interceptor 执行
 *
 * <h1>执行顺序</h1> 过滤前-拦截前-Action处理-拦截后-过滤后
 */
public class HandlerInterceptorDemo implements HandlerInterceptor {

    private static Logger logger = LoggerFactory.getLogger(HandlerInterceptorDemo.class);

    private final AtomicLong count = new AtomicLong(0);

    private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("StopWatch-StartTime");

    /**
     * <li>预处理回调方法，实现处理器的预处理（即在处理方法被调用之前生效）。
     * <li>返回值：true表示继续流程（如调用下一个拦截器或处理器）；false表示流程中断，不会继续调用其他的拦截器或处理器，此时需要通过response来产生响应；
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        //获取请求的最佳匹配路径
        String pattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
        //获取@PathVariable的值
        Map attribute = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        logger.info(handler.getClass().toString());
        logger.info("handler instanceof HandlerMethod : {}", handler instanceof HandlerMethod);
        if(handler instanceof HandlerMethod){
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            logger.info("handlerMethod: {}", handlerMethod);
        }

        // 开始时间
        long beginTime = System.currentTimeMillis();

        // 线程绑定变量（该数据只有当前请求的线程可见）
        startTimeThreadLocal.set(beginTime);
        MappedInterceptor a = null;
        /**
         * 对来自后台的请求统一进行处理
         */
        long tmp = count.incrementAndGet();
        String url = request.getRequestURL().toString();
        String method = request.getMethod();
        String uri = request.getRequestURI();
        String queryString = request.getQueryString();
        Map<String, String[]> parameterMap = request.getParameterMap();
        logger.info("parameterMap:{}", parameterMap.toString());
        logger.info(String.format("请求参数, url: %s, method: %s, uri: %s, params: %s", url, method, uri, queryString));

        HttpSession session = request.getSession();
        Object user = session.getAttribute("user");
        if (user == null) {
            // response.sendError(-1, "need login .");
            // response.sendRedirect("/toLogin");
            // return false;
            return true;
        } else {
            return true;
        }

    }

    /**
     * <li>后处理回调方法，实现处理器的后处理（但在渲染视图之前）。
     * <li>此时可以通过 modelAndView（模型和视图对象）对模型数据进行处理或对视图进行处理，modelAndView 也可能为 null。
     */
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                           Object handler, ModelAndView modelAndView) throws Exception {
        logger.info("postHandle");
    }

    /**
     * <li>整个请求处理完毕回调方法，即在视图渲染完毕时回调。
     * <li>类似于try-catch-finally中的finally，但仅调用处理器执行链中 preHandle 返回 true 的拦截器的 afterCompletion。
     */
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                                Object handler, Exception e) throws Exception {
        logger.info("afterCompletion");
        // 计算页面加载时间
        long endTime = System.currentTimeMillis();
        long beginTime = startTimeThreadLocal.get();
        long consumeTime = endTime - beginTime;

        // 大于 500 毫秒表示请求为慢请求
        if (consumeTime > 500) {
            // 记录日志
            logger.info(String.format("%s consume %d millis", httpServletRequest.getRequestURI(), consumeTime));
        }
    }
}